package be.stijn.gdocs.services;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gdata.client.GoogleAuthTokenFactory.UserToken;
import com.google.gdata.client.docs.DocsService;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.MediaContent;
import com.google.gdata.data.docs.DocumentListEntry;
import com.google.gdata.data.media.MediaSource;
import com.google.gdata.util.ServiceException;

public class DownloadService {

    private final DocsService docService;
    private final SpreadsheetService spreadSheetService;
    private final Logger logger = LoggerFactory.getLogger(DownloadService.class);

    /**
     * Constructor
     * 
     * @param service
     *            The service used to download the documents.
     * @param spreadSheetService
     *            The service who's token will be used to download spreadsheets, because spreadsheets require a
     *            different authentication.
     */
    public DownloadService(DocsService service, SpreadsheetService spreadSheetService) {
        this.docService = service;
        this.spreadSheetService = spreadSheetService;
    }

    /**
     * Downloads document on google docs to the file system.
     * 
     * @param entry
     *            The document entry who needs to be downloaded.
     * @param destination
     *            The location of the directory on the file sytem where the file needs to be downloaded to.
     * @throws MalformedURLException
     *             When no valid url could be created using the file entry.
     * @throws IOException
     *             When the file could not be written to the file system.
     * @throws ServiceException
     *             When an error occurred in the google service.
     */
    public void download(DocumentListEntry entry, String destination) throws MalformedURLException, IOException,
            ServiceException {
        String documentTitle = entry.getTitle().getPlainText();

        if (DocumentType.DOCUMENT.toString().equals(entry.getType())) {
            downloadDocument(entry, destination
                    + documentTitle
                    + ExportType.DOC.getFileExtension(), ExportType.DOC);
            return;
        }
        if (DocumentType.SPREADSHEET.toString().equals(entry.getType())) {
            downloadSpreadSheet(entry, destination + documentTitle
                    + ExportType.XLS.getFileExtension(), ExportType.XLS);
            return;
        }
        if (DocumentType.PRESENTATION.toString().equals(entry.getType())) {
            downloadDocument(entry, destination + documentTitle + ExportType.PPT.getFileExtension(), ExportType.PPT);
            return;
        }
        if (DocumentType.DRAWING.toString().equals(entry.getType())) {
            downloadDocument(entry, destination + documentTitle + ExportType.PNG.getFileExtension(), ExportType.PNG);
            return;
        }
        if (DocumentType.PDF.toString().equals(entry.getType())) {
            downloadNativeFile(entry, destination + documentTitle);
            return;
        }

        try {
            downloadNativeFile(entry, destination + documentTitle);
        }
        catch (Exception e) {
            logger.error(String.format("Could not download file %s of type: %s", documentTitle,
                    entry.getType()), e);
        }

    }

    /**
     * Creates a new file object and created its parent directories on the file system.
     * 
     * @param filepath
     *            The file path of the file who needs to be created.
     * @return The file who's created.
     */
    private File createFile(String filepath) {
        File f = new File(filepath);
        f.getParentFile().mkdirs();
        return f;
    }

    /**
     * Download a document.
     * 
     * @param entry
     *            The document entry who needs to be downloaded
     * @param filepath
     *            The destination of the file who needs to be downloaded, including the file name and extension.
     * @param exportType
     *            The export type (could be doc, rtf, ..)
     * @throws IOException
     *             When the file could not be written to the file system.
     * @throws MalformedURLException
     *             When the used url was incorrect.
     * @throws ServiceException
     *             When an excpetion occured inside the google service.
     */
    private void downloadDocument(DocumentListEntry entry, String filepath, ExportType exportType)
            throws IOException, MalformedURLException, ServiceException {
        String exportUrl = ((MediaContent) entry.getContent()).getUri() + "&exportFormat=" + exportType.toString();
        downloadFile(new URL(exportUrl), filepath);
    }

    /**
     * Downloads a spreadSheet file using the spreadsheet service token and the spreadsheet export url.
     * 
     * @param entry
     *            The spreadsheets entry who needs to be downloaded.
     * @param filepath
     *            The destination of the file who needs to be downloaded, including the file name and extension.
     * @param exportType
     *            The export type (could be xsl, but also csv etc)
     * @throws IOException
     *             When the file could not be written to the file system.
     * @throws MalformedURLException
     *             When the used url was incorrect.
     * @throws ServiceException
     *             When a exception occured inside the google service.
     */
    private void downloadSpreadSheet(DocumentListEntry entry, String filepath, ExportType exportType)
            throws IOException, MalformedURLException, ServiceException {

        logger.trace("Using the spreadsheet autorization token...");
        // For spreadsheets we need a different authorization token
        // We need to set the token from the spreadSheet service to the doc service
        UserToken docsToken = (UserToken) docService.getAuthTokenFactory()
                .getAuthToken();
        UserToken spreadsheetsToken = (UserToken) spreadSheetService
                .getAuthTokenFactory().getAuthToken();
        docService.setUserToken(spreadsheetsToken.getValue());

        String exportUrl = "https://spreadsheets.google.com/feeds/download/spreadsheets" +
                "/Export?key=" + entry.getDocId() + "&exportFormat=" + exportType.toString();
        downloadFile(new URL(exportUrl), filepath);

        // Restore docs token
        docService.setUserToken(docsToken.getValue());
        logger.trace("Restored the document autorization token.");
    }

    /**
     * Downloads a native file like a pdf (gdcos non-editable files like documents, spreadsheets, ...)
     * 
     * @param entry
     *            The entry who needs to be downloaded.
     * @param filepath
     *            The destination of the file who needs to be downloaded, including the file name and extension.
     * @throws IOException
     *             When the file could not be written to the file system.
     * @throws ServiceException
     *             When an error occured with the Google service.
     */
    private void downloadNativeFile(DocumentListEntry entry, String filepath) throws IOException, ServiceException {
        MediaContent mc = (MediaContent) entry.getContent();
        // TODO See example
        // String fileExtention = mc.getMimeType().getSubType();
        URL exportURL = new URL(mc.getUri());
        downloadFile(exportURL, filepath);
    }

    /**
     * Downloads the file to the file system.
     * 
     * @param exportUrl
     *            The URL of the Google document who needs to be downloaded.
     * @param filepath
     *            The destination of the file who needs to be saved, including the file name and extension.
     * @throws IOException
     *             When the file could not be written to the file system.
     * @throws MalformedURLException
     *             When the given url was incorrect.
     * @throws ServiceException
     *             When an error occurred with the Google service.
     */
    private void downloadFile(URL exportUrl, String filepath) throws IOException,
            MalformedURLException, ServiceException {

        File f = createFile(filepath);

        MediaContent mc = new MediaContent();
        mc.setUri(exportUrl.toString());
        MediaSource ms = docService.getMedia(mc);

        InputStream inStream = null;
        FileOutputStream outStream = null;

        try {
            inStream = ms.getInputStream();
            outStream = new FileOutputStream(f);

            int c;
            while ((c = inStream.read()) != -1) {
                outStream.write(c);
            }
        }
        finally {
            if (inStream != null) {
                inStream.close();
            }
            if (outStream != null) {
                outStream.flush();
                outStream.close();
            }
            logger.trace(String.format("%s has been downloaded succesfully", filepath));
        }
    }
}
